package org.gpp.proj1.ui.gui.ingame;

import java.io.File;
import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.gpp.proj1.bone.BoneStructure;
import org.gpp.proj1.bone.parser.BoneStructureParser;
import org.gpp.proj1.bone.parser.BoneStructureParserException;
import org.gpp.proj1.camera.CameraDirector;
import org.gpp.proj1.camera.action.LookAtPointAction;
import org.gpp.proj1.camera.action.MoveToPointAction;
import org.gpp.proj1.camera.action.SetFocusAction;
import org.gpp.proj1.communication.CommunicationException;
import org.gpp.proj1.logic.core.board.FacingSides;
import org.gpp.proj1.logic.core.board.RobotPosition;
import org.gpp.proj1.logic.metadata.PartCategory;
import org.gpp.proj1.logic.metadata.PartEntry;
import org.gpp.proj1.logic.metadata.RobotPartCategory;
import org.gpp.proj1.logic.shop.list.PartListLoader;
import org.gpp.proj1.map.GameMap;
import org.gpp.proj1.map.MapNode;
import org.gpp.proj1.map.renderer.MapNodeBuilder;
import org.gpp.proj1.robot.Robot;
import org.gpp.proj1.robot.RobotImpl;
import org.gpp.proj1.robot.part.SparePart;
import org.gpp.proj1.robot.part.parser.InvalidPartFileException;
import org.gpp.proj1.robot.part.parser.PartFileParser;
import org.gpp.proj1.ui.controller.ISRGUIController;
import org.gpp.proj1.util.TextureStateFactory;

import com.jme.image.Texture;
import com.jme.input.AbsoluteMouse;
import com.jme.input.FirstPersonHandler;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.input.MouseInput;
import com.jme.intersection.BoundingPickResults;
import com.jme.intersection.PickResults;
import com.jme.light.DirectionalLight;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Ray;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.CameraNode;
import com.jme.scene.Node;
import com.jme.scene.shape.Quad;
import com.jme.scene.state.AlphaState;
import com.jme.scene.state.LightState;
import com.jme.scene.state.TextureState;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;
import com.jmex.game.state.CameraGameState;

/**
 * @author Paolo Victor, paolovictor@gmail.com
 */
public class GameBoardState extends CameraGameState {

	/**
	 * The board state name, used by the state manager
	 */
	public static final String GAME_BOARD_STATE_NAME = "Game Board";

	/**
	 * The default humanoid bone structure file
	 */
	private static final String ROBOT_BONE_STRUCTURE_FILE = "res/bone/humanoid.bon";

	/**
	 * The robot's default animation
	 */
	private static final String DEFAULT_ANIMATION = "stopped";

	/**
	 * The quit event id
	 */
	private static final String QUIT = "quit";

	private static final int DEFAULT_ATTACK_RANGE = 3;

	/**
	 * Absolute mouse used for picking
	 */
	private AbsoluteMouse absoluteMouse;

	/**
	 * The game map metadata
	 */
	private final GameMap map;

	/**
	 * The robots' initial positions
	 */
	private final Map<String, Map<String, RobotPosition>> positions;
	
	private final Robot[][] robotsByPosition;
	
	private List<RobotPosition> movementExceptions;
	
	/**
	 * The robots' nodes
	 */
	private final Map<String, Map<String, Node>> robotNodes;

	/**
	 * The clients' robots metadata
	 */
	private final Map<String, Map<String, Map<RobotPartCategory, String>>> robotMetadata;

	/**
	 * The clients' robot objects, created from the metadata
	 */
	private Map<String, Map<String, Robot>> robots;

	/**
	 * The parent view. Used to communicate with the game client
	 */
	private final ISRGUIController controller;

	/**
	 * Used to check if the board has been initialized but not started
	 */
	private boolean isIdle;

	/**
	 * The scene camera node used by the director
	 */
	private CameraNode cameraNode;

	/**
	 * The scene camera director
	 */
	private CameraDirector director;

	/**
	 * The board's map
	 */
	private MapNode mapNode;

	/**
	 * Input handler for mouse events
	 */
	private FirstPersonHandler handler;

	/**
	 * The node corresponding to the previous robot on the turn rotation
	 */
	private Node previousRobotNode;
	
	/**
	 * The id of the player that holds the current turn
	 */
	private String currentTurnPlayer;
	
	/**
	 * The identification for the robot controlled on the current turn
	 */
	private String currentTurnRobot;

	/**
	 * Back reference to this class' viewport manager
	 */
	private final GameViewportManager manager;

	/**
	 * <code>true</code> if the a movement selection
	 * confirmation window is opened
	 */
	private boolean askingForMovementConfirmation = false;
	
	/**
	 * <code>true</code> if the a attack selection
	 * confirmation window is opened
	 */
	private boolean askingForAttackConfirmation = false;

	/**
	 * The selected X coordinate on the movement selector
	 */
	private int selectedX;

	/**
	 * The selected Y coordinate on the movement selector
	 */
	private int selectedY;
	
	/**
	 * <code>true</code> if the board is currently accepting
	 * events from the server
	 */
	private boolean acceptingMessages;

	/**
	 * Quad for the cursor that appears bellow the robots
	 */
	private Quad cursorTile;
	
	/**
	 * The current rotation for the cursor tile
	 */
	private float cursorTileRotation = 0.0f;

	private Robot currentRobot;

	/**
	 * @param controller The game controller
	 * @param map The game map metadata
	 * @param positions The robots' initial positions
	 * @param robotsMetadata The clients' robots metadata
	 * @param manager 
	 */
	public GameBoardState(ISRGUIController controller, GameMap map, Map<String, Map<String, RobotPosition>> positions, Map<String, Map<String, Map<RobotPartCategory, String>>> robotsMetadata, GameViewportManager manager ) {
		super( GAME_BOARD_STATE_NAME );

		this.controller = controller;
		this.map = map;
		this.positions = positions;
		this.robotMetadata = robotsMetadata;
		this.manager = manager;

		this.robots = new HashMap<String, Map<String, Robot>>();
		this.robotNodes = new HashMap<String, Map<String, Node>>();
		this.movementExceptions = new LinkedList<RobotPosition>();
		this.robotsByPosition = new Robot[ map.getMapWidth() ][ map.getMapLength() ];

		this.previousRobotNode = null;		
		this.acceptingMessages = true;
		this.isIdle = true;

		try {
			this.initializeRobots();
		} catch (FileNotFoundException e) {
			e.printStackTrace(); // That's bad. Notify the interface and leave. It's a critical error.
			System.exit( 0 );
		} catch (InvalidPartFileException e) {
			e.printStackTrace(); // That's bad. Notify the interface and leave. It's a critical error.
			System.exit( 0 );
		} catch (BoneStructureParserException e) {
			e.printStackTrace(); // TODO That's bad. Notify the interface and leave. It's a critical error.
			System.exit( 0 );
		}

		// Loading map
		initMap(map);

		// Initialising light sate
		initLightState();

		// Setting up camera and director		
		initializeCamera();

		// Initialising input
		this.initInput();
	}

	private void initializeCamera() {
		cam.setLocation( new Vector3f( 0, 300, 0 ) );
		cam.lookAt( new Vector3f( 200, 0, 200 ), new Vector3f( 0, 1, 0 ) );

		this.cameraNode = new CameraNode( "CameraNode", cam );
		this.cameraNode.setLocalTranslation( new Vector3f( 0, 300, 0 ) );
		this.director = new CameraDirector( cameraNode );
	}

	private void initMap(GameMap map) {
		if( map != null ) {
			mapNode = MapNodeBuilder.buildNode( map );
			rootNode.attachChild( mapNode );
		}
	}

	private void initLightState() {
		LightState lightState = DisplaySystem.getDisplaySystem().getRenderer().createLightState();
		DirectionalLight light = new DirectionalLight();
		light.setDirection( new Vector3f( 0.4f, -1.0f, 0.4f ) );
		light.setAmbient( ColorRGBA.white );
		light.setDiffuse( ColorRGBA.white );
		light.setEnabled( true );
		lightState.attach( light );

		rootNode.setRenderState( lightState );
		rootNode.updateGeometricState(0, true);
		rootNode.updateRenderState();
	}		

	/**
	 * Loads and initializes the player's robots. It load the parts list,
	 * builds the Robots and places then on the game map
	 * 
	 * @throws FileNotFoundException
	 * @throws InvalidPartFileException
	 * @throws BoneStructureParserException
	 */
	private void initializeRobots() throws FileNotFoundException, InvalidPartFileException, BoneStructureParserException {
		// Loads parts list
		Map<PartCategory, Map<String, PartEntry>> parts;
		BoneStructureParser parser = new BoneStructureParser( ROBOT_BONE_STRUCTURE_FILE );
		parts = PartListLoader.loadParts( PartListLoader.DEFAULT_PARTS_LIST );

		for( String clientID : robotMetadata.keySet() ) {
			Map<String, Map<RobotPartCategory, String>> clientData = robotMetadata.get( clientID );

			this.robots.put( clientID, new HashMap<String, Robot>() );
			this.robotNodes.put( clientID, new HashMap<String, Node>() );

			for( String robotName : clientData.keySet() ) {
				// Loading robot parts
				Map<RobotPartCategory, String> robotData = clientData.get( robotName );

				SparePart head = PartFileParser.parseFile( parts.get( PartCategory.HEAD ).get( robotData.get( RobotPartCategory.HEAD ) ).getFile() );
				SparePart torso = PartFileParser.parseFile( parts.get( PartCategory.TORSO ).get( robotData.get( RobotPartCategory.TORSO ) ).getFile() );
				SparePart armLeft = PartFileParser.parseFile( parts.get( PartCategory.ARM ).get( robotData.get( RobotPartCategory.ARM_LEFT ) ).getFile() );
				SparePart armRight = PartFileParser.parseFile( parts.get( PartCategory.ARM ).get( robotData.get( RobotPartCategory.ARM_RIGHT ) ).getFile() );
				SparePart legs = PartFileParser.parseFile( parts.get( PartCategory.LEGS ).get( robotData.get( RobotPartCategory.LEGS ) ).getFile() );

				// Creating robot and assigning its parts
				BoneStructure boneStructure = parser.getBoneStructure();
				Robot robot = new RobotImpl( boneStructure );

				robot.setPart( RobotPartCategory.HEAD.getPartTag(), head );
				robot.setPart( RobotPartCategory.TORSO.getPartTag(), torso );
				robot.setPart( RobotPartCategory.ARM_LEFT.getPartTag(), armLeft );
				robot.setPart( RobotPartCategory.ARM_RIGHT.getPartTag(), armRight );
				robot.setPart( RobotPartCategory.LEGS.getPartTag(), legs );

				robot.setAnimation( DEFAULT_ANIMATION );

				robot.getNode().updateRenderState();

				// Adding robot to scene graph
				Node robotNode = new Node( clientID + ":" + robotName );
				robotNode.attachChild( robot.getNode() );

				this.robotNodes.get( clientID ).put( robotName, robotNode );				
				this.rootNode.attachChild( robotNode );				

				// 0, 0 is the upper left
				// SOUTH is at angle 0
				// Positioning the robot
				RobotPosition position = positions.get( clientID ).get( robotName );

				updateRobotPosition(robotNode, position);
				
				this.movementExceptions.add( position );
				
				robotsByPosition[ position.getY() ][ position.getX() ] = robot;

				// Rotating the robot accordingly
				Quaternion rotation = new Quaternion();
				rotation.fromAngles( new float[]{ 0, 0, 0, } );
				if( position.getFacing().equals( FacingSides.NORTH ) ) {
					rotation.fromAngles( new float[]{ 0, FastMath.PI, 0, } );
				} else if( position.getFacing().equals( FacingSides.EAST ) ) {
					rotation.fromAngles( new float[]{ 0, FastMath.PI / 2, 0, } );
				} else if( position.getFacing().equals( FacingSides.WEST ) ) {
					rotation.fromAngles( new float[]{ 0, - FastMath.PI / 2, 0, } );
				}
				robotNode.setLocalRotation( rotation );

				robotNode.updateGeometricState( 0.0f, true );

				// Adding robot to robot mapping
				this.robots.get( clientID ).put( robotName, robot );
			}
		}
	}

	/**
	 * Updates an robot position accodring the map layout
	 * @param robotNode
	 * @param position
	 */
	private void updateRobotPosition(Node robotNode, RobotPosition position) {
		float x = position.getX() * MapNode.TILE_SIZE + MapNode.TILE_SIZE / 2;
		float y = position.getY() * MapNode.TILE_SIZE;
		float z = map.getHeights()[position.getY()][position.getX()] * MapNode.TILE_HEIGHT;
		
		robotNode.setLocalTranslation( x, z, y );
	}

	@Override
	protected void stateUpdate(float tpf) {
		// If the board has just been initialized, notify the server
		if( this.isIdle ) {
			try {
				this.controller.notifyReadyToStartGame();
			} catch (CommunicationException e) {
				// TODO BAAAD! Notify interface, stop and go back to lobby
			}
			this.isIdle = false;
		}

		notifyController();
		updateSelectors();
		updateCursorTile();
		handleQuitEvent();
		
		// updating robots
		for( String player : this.robots.keySet() ) {
			for( Robot robot : this.robots.get( player ).values() ) {
				robot.updateAnimation( tpf );
			}
		}
		
		// Updating input handler
		handler.update( tpf );
		
		// Updating camera
		director.updateCamera( tpf );
	}

	private void handleQuitEvent() {
		if (KeyBindingManager.getKeyBindingManager().isValidCommand(QUIT, false)) {
			this.cleanup();
			System.exit( 0 );
		}
	}

	private void notifyController() {
		// Pokes the controller if it's accepting messages
		if( this.acceptingMessages ) {
			this.controller.uiAcceptingMessages();
		} else {
			// If director is idle and messages were being ignored, check if it's
			// the player's turn
			if( this.director.isIdle() && this.currentRobot.isIdle() ) {
				this.acceptingMessages = true;			
				this.mapNode.deactivateCursorTile();
				this.manager.showMenu();
			}
		}
	}

	private void updateCursorTile() {
		cursorTileRotation += 0.05f;
		if( this.cursorTile != null ) {
			this.cursorTile.getLocalRotation().fromAngles( - FastMath.PI / 2, cursorTileRotation, 0.0f );
			this.cursorTile.getLocalRotation().normalize();
		}
	}

	private void updateSelectors() {
		// If it's not asking for movement confirmation...
		if( ! this.askingForMovementConfirmation && ! this.askingForAttackConfirmation ){
			
			// And the mouse is clicked...
			if( MouseInput.get().isButtonDown(0) ) {
				Ray mouseRay = this.getMouseRay();

				// If the mouse is over some tile, select it
				if( this.checkPick( mouseRay ) ) {
					if( mapNode.isMovementSelectorOpen() ) {
						this.askingForMovementConfirmation  = true;
					} else if( mapNode.isAttackSelectorOpen() ) {
						this.askingForAttackConfirmation  = true;
					}
					this.manager.askForConfirmation();
				} else {
					// If the player clicks outside the selection, close it
					this.closeMovementSelector();
					this.manager.movementSelectionCancelled();
				}
			}
			
			// If the selector is enabled and the mouse hovers it,
			// highlight the tile
			if( this.mapNode.isMovementSelectorOpen() || this.mapNode.isAttackSelectorOpen() ) {
				Ray mouseRay = this.getMouseRay();
				this.checkPick( mouseRay );
			}
		}
	}

	/**
	 * Gets called from super constructor. Sets up the input handler that let
	 * us walk around using the w,s,a,d keys and mouse.
	 */
	private void initInput() {
		// Bind the exit action to the escape key.
		KeyBindingManager.getKeyBindingManager().set( QUIT, KeyInput.KEY_ESCAPE);

		handler = new FirstPersonHandler( this.cam, "inputHandler" );
		handler.setEnabled( true );

		// Initializing mouse
		this.createMouse();
		this.setMouse();
	}

	public void notifyRobotTurn(String robotName) {

		if( this.previousRobotNode != null ) {
			this.detachSelectionQuad( previousRobotNode );
		}

		this.currentTurnRobot = robotName;

		Node robotNode = this.robotNodes.get( currentTurnPlayer ).get( robotName );

		this.previousRobotNode = robotNode;
		
		this.currentRobot = this.robots.get( currentTurnPlayer ).get( robotName );

		this.focusOnRobot(robotNode);

		this.attachSelectionQuad( robotNode );
	}

	private void focusOnRobot(Node robotNode) {
		Vector3f position = robotNode.getLocalTranslation();
		director.setFocusedObject( null );
		director.queueAction( new LookAtPointAction( cameraNode, robotNode.getLocalTranslation(), 0.5f ) );
		director.queueAction( new SetFocusAction( director, robotNode ) );
		director.queueAction( new MoveToPointAction( cameraNode, new Vector3f(position.x + 30, position.y + 180, position.z + 90 ), 0.5f ) );
	}

	private void detachSelectionQuad(Node node) {

		node.detachChildNamed( "Cursor" );
		node.updateGeometricState( 0, true );
	}

	private void attachSelectionQuad( Node node ) {

		this.cursorTile = new Quad( "Cursor", MapNode.TILE_SIZE, MapNode.TILE_SIZE);

		Quaternion quat = new Quaternion();
		quat.fromAngles( - FastMath.PI / 2, 0.0f, 0.0f );
		cursorTile.setLocalRotation( quat );

		cursorTile.setLocalTranslation( new Vector3f( 0.0f, 1.0f, 0.0f ) );
		cursorTile.setRenderQueueMode( Renderer.QUEUE_TRANSPARENT );
		
		AlphaState alphaState = DisplaySystem.getDisplaySystem().getRenderer().createAlphaState();
        alphaState.setBlendEnabled( true );
        alphaState.setSrcFunction( AlphaState.SB_SRC_ALPHA );
        alphaState.setDstFunction( AlphaState.DB_ONE_MINUS_SRC_ALPHA );
        alphaState.setTestEnabled(true);
        alphaState.setEnabled( true );
       
		// Texturing
		TextureState ts = TextureStateFactory.getInstance().getTextureState( "res/images/selectionCursor.tga" );
		ts.setEnabled( true );

		cursorTile.setRenderState(alphaState);
		cursorTile.setRenderState(ts);
		cursorTile.updateRenderState();
		cursorTile.updateGeometricState( 0, true );

		node.attachChild( cursorTile );
		node.updateRenderState();
		node.updateGeometricState( 0, true );
	}

	public void notifyTurn(String clientId) {

		this.currentTurnPlayer = clientId;
	}

	public void notifyOwnTurn() {

		try {
			this.currentTurnPlayer = this.controller.getCurrentPlayerId();
		} catch (CommunicationException e) {
			// BAAD!
		}
	}

	public void openMovementSelector() {

		Robot robot = this.robots.get( currentTurnPlayer ).get( this.currentTurnRobot );
		RobotPosition position = this.positions.get( currentTurnPlayer ).get( this.currentTurnRobot );
		this.mapNode.activateSelectionTiles( position.getY(), position.getX(), robot.getMovementRange(), ColorRGBA.green, this.movementExceptions );
	}

	public void closeMovementSelector() {

		this.mapNode.deactivateSelectionRegion();
		this.mapNode.deactivateCursorTile();
	}

	/**
	 * Obtains the Ray for the current mouse position.
	 * @return
	 */
	private Ray getMouseRay() {
		Vector2f screenPos = new Vector2f();
		// Get the position that the mouse is pointing to
		screenPos.set( absoluteMouse.getHotSpotPosition().x, absoluteMouse.getHotSpotPosition().y );
		// Get the world location of that X,Y value
		Vector3f nearWorldCoords = DisplaySystem.getDisplaySystem().getWorldCoordinates(screenPos, 0);
		Vector3f farWorldCoords = DisplaySystem.getDisplaySystem().getWorldCoordinates(screenPos, 1);
		// Create a ray starting from the camera, and going in the direction
		// of the mouse's location
		return new Ray( nearWorldCoords, farWorldCoords.subtractLocal( nearWorldCoords ).normalizeLocal());		
	}

	/**
	 * Check intersections between ray and scene elements and
	 * address solution in positive cases.
	 * @param mouseRay
	 * @return 
	 */
	private boolean checkPick(Ray mouseRay) {
		// list for the intersected selection tiles
		PickResults pr = new BoundingPickResults();

		// test intersection of ray with selection tiles
		this.mapNode.getSelectionTiles().findPick(mouseRay, pr);

		// if list has elements, some tile was hit by the ray
		if (pr.getNumber() > 0) {
			// use the name of tile Quad to discover its position in map
			String selectedTile = pr.getPickData( 0 ).getTargetMesh().getParentGeom().getName();
			int[] coords = this.mapNode.getTilePositionInMap( selectedTile ); 

			// draw cursor tile
			this.mapNode.activateCursorTile(coords[0], coords[1], new ColorRGBA(1.0f, 0.0f, 0.0f, 0.5f));
			
			this.selectedX = coords[1];
			this.selectedY = coords[0];

			return true;
		}
		else {
			// remove cursor tile
			this.mapNode.deactivateCursorTile();

			return false;
		}
	}

	/**
	 * Configure mouse interaction and behaviour.
	 */
	private void setMouse() {
		// Get the mouse input device and assign it to the AbsoluteMouse
		// Move the mouse to the middle of the screen to start with
		absoluteMouse.setLocalTranslation(new Vector3f(DisplaySystem.getDisplaySystem().getWidth() / 2, DisplaySystem.getDisplaySystem().getHeight() / 2, 0));
		// Assign the mouse to an input handler
		absoluteMouse.registerWithInputHandler( handler );
		// stop Quake's "mouse look" effect (mouse doesn't move camera anymore)
		handler.getMouseLookHandler().setEnabled( false );
		// Attach mouse to root node
		rootNode.attachChild(absoluteMouse);
	}

	
	/**
	 * Create mouse and set the cursor appearence.
	 */
	private void createMouse() {
		// Create a new mouse. Restrict its movements to the display screen.
		absoluteMouse = new AbsoluteMouse("Mouse", DisplaySystem.getDisplaySystem().getWidth(), DisplaySystem.getDisplaySystem().getHeight());

		// Get a picture for my mouse.
		TextureState ts = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
		URL cursorLoc = null;
		try {
			cursorLoc = new File("res/images/cursor1.png").toURI().toURL();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
		Texture t = TextureManager.loadTexture(cursorLoc, Texture.MM_LINEAR, Texture.FM_LINEAR);
		ts.setTexture(t);
		absoluteMouse.setRenderState(ts);

		// Make the mouse's background blend with what's already there
		AlphaState as = DisplaySystem.getDisplaySystem().getRenderer().createAlphaState();
		as.setBlendEnabled(true);
		as.setSrcFunction(AlphaState.SB_SRC_ALPHA);
		as.setDstFunction(AlphaState.DB_ONE_MINUS_SRC_ALPHA);
		as.setTestEnabled(true);
		as.setTestFunction(AlphaState.TF_GREATER);
		absoluteMouse.setRenderState(as);		
		absoluteMouse.updateRenderState();
	}

	public void selectionConfirmed() {

		this.mapNode.deactivateSelectionRegion();
		this.mapNode.deactivateCursorTile();
		
		if( askingForMovementConfirmation ) {
			this.askingForMovementConfirmation = false;
			
			try {
				this.controller.movedRobot( this.currentTurnRobot, this.selectedY, this.selectedX );
			} catch (CommunicationException e) {
				e.printStackTrace();
				System.exit( 1 );
			}
		} else if( askingForAttackConfirmation ) {
			this.askingForAttackConfirmation = false;
			
			try {
				this.controller.robotAttacked( this.currentTurnRobot,
						this.selectedX,
						this.selectedY,
						this.manager.getSelectedPart(),
						this.manager.getSelectedAttack());
			} catch (CommunicationException e) {
				e.printStackTrace();
				System.exit( 1 );
			}
		}
	}

	public void selectionUnconfirmed() {

		this.askingForMovementConfirmation = false;
		this.askingForAttackConfirmation = false;
	}
	
	public void moveRobot( String player, String robot, int y, int x ) {
		
		this.acceptingMessages = false;
		
		this.mapNode.activateCursorTile( y, x, new ColorRGBA(1.0f, 0.0f, 0.0f, 0.5f));
		
		RobotPosition position = positions.get( player ).get( robot );
		
		this.robotsByPosition[ position.getY() ][ position.getX() ] = null;
		this.robotsByPosition[ y ][ x ] = this.robots.get( player ).get( robot );
		
		position.setX( x );
		position.setY( y );
		
		Node robotNode = this.robotNodes.get( player ).get( robot );
		this.updateRobotPosition( robotNode, position );
		
		this.focusOnRobot( robotNode );
	}

	public Robot getCurrentTurnRobot() {
		
		return this.currentRobot;
	}

	public void displayTargetSelectionField() {
		
		RobotPosition position = this.positions.get( currentTurnPlayer ).get( this.currentTurnRobot );

		Set<RobotPosition> pos = new HashSet<RobotPosition>();
		for( String player : this.positions.keySet() ) {
			pos.addAll( this.positions.get( player ).values() );
		}
		
		Collection<RobotPosition> exceptions = this.positions.get( currentTurnPlayer ).values(); 
		this.mapNode.activateAttackSelectionTiles( position.getY(), position.getX(), DEFAULT_ATTACK_RANGE, ColorRGBA.orange, pos, exceptions );
	}

	public void notifyRobotAttacked(String player, String robotName, int x, int y, String part, String attack) {

		this.acceptingMessages = false;
		
		Robot robot = this.robots.get( player ).get( robotName );
				
		if( robotsByPosition[y][x] != null ) {
			Robot target = robotsByPosition[y][x];
			
			Node n1 = this.robotNodes.get( player ).get( robotName );
			Node n2 = new Node( "Dummy" );
			this.updateRobotPosition(n2, new RobotPosition( null, x, y ) );
			
			float angle = FastMath.atan2( n1.getWorldTranslation().y - n2.getWorldTranslation().y,
					n1.getWorldTranslation().x - n2.getWorldTranslation().x );
			
			n1.getWorldRotation().fromAngles( 0.0f, angle, 0.0f );
			n1.getWorldRotation().normalize();
			n1.updateGeometricState( 0.0f, true );
			
			List<Robot> targets = new LinkedList<Robot>();
			targets.add( robotsByPosition[y][x] );
	
			robot.executeAction( part, attack, targets );
		} else {
			robot.executeAction( part, attack );
		}
			
	}
}
